// Copyright (c) 2009, Michael Patraw
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * The name of Michael Patraw may not be used to endorse or promote 
//       products derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <cassert>

#include "MersenneTwister.h"
#include "tui.h"
#include "util.h"
#include "world.h"

extern MTRand mt;

World::World(Map * map, const char * name) :
map_(map), player_(name)
{
    player_.coord().setX(mt.randInt(map_->getWidth() - 1));
    player_.coord().setY(mt.randInt(map_->getHeight() - 1));
}

World::~World()
{
    if (map_)
        delete map_;
}

Map * World::map()
{
    return map_;
}

void World::map(Map * map)
{
    if (map_)
        delete map_;
    map_ = map;
}

Human * World::player()
{
    return &player_;
}

void World::player(Human & hum)
{
    player_ = hum;
}

const Time * World::getTime() const
{
    return &time_;
}

const Weather * World::getWeather() const
{
    return &weather_;
}

void World::update()
{
    static int day = -1;
    time_.update();
    weather_.update(time_);
    player_.setWarmth(weather_.getCTempMod());
    // Update the players health by 3 if a new day has come.
    if (time_.getDay() > day) {
        day = time_.getDay();
        player_.health()->current() += 3;
        if (player_.health()->current() > player_.health()->max())
            player_.health()->current() = player_.health()->max();
    }
    player_.update();
}

bool World::canMoveTo(int x, int y)
{
    if (x < 0 || x >= map_->getWidth() || y < 0 || y >= map_->getHeight())
        return false;
    
    if (!map_->getTile(x, y)->isPassable())
        return false;
    
    return true;
}

//==============================================================================
//==============================================================================

bool World::moveCommand(Dir d)
{
    int dx, dy;
    GetDirection(d, &dx, &dy);
    
    if (!canMoveTo(player_.getCoord().getX() + dx, player_.getCoord().getY() + dy))
        return false;
    
    player_.coord().modX(dx);
    player_.coord().modY(dy);

    return true;
}

void World::restCommand()
{
    // Negate the energy ticks. TODO: Temporarily decrease temperature.
    player_.modEnergy(1);
}

void World::sleepCommand()
{
    for (unsigned int i = 0; i < (Time::TURNS_IN_HOUR * 8); ++i) {
        player_.modEnergy(2);
        update(); // Update the world.
        MessWindow()->addstr(0, 1, "You wake up", White);
    }
}

// TODO Needs work on the 'use' part, eat and stuff.
void World::viewinventoryCommand()
{
    ItemList * list = player_.getInv();
    Item * temp;
    int pos, size = list->numItems();
    
    do {
        pos = selectItem(list, "= Viewing Inventory, press ENTER to equip =");
        
        if (pos >= 0 && pos < size) {
            // Try placing in a slot.
            temp = player_.equip(list->remItem(pos));
            list->addItem(temp);
        }
    } while (pos != -1);
}

void World::viewequippedCommand()
{
    ItemList list;
    list.addItem(player_.getHand());
    list.addItem(player_.getHead());
    list.addItem(player_.getBody());
    list.addItem(player_.getLegs());
    list.addItem(player_.getFeet());
    Item * temp;
    int pos, size = list.numItems();
    
    do {
        temp = NULL;
        pos = selectItem(&list, "= Viewing Equipped, press ENTER to remove =");
        
        if (pos >= 0 && pos < size) {
            if (player_.getInv()->isFull()) {
                MessWindow()->addstr(0, 0, "No inventory space.", White);
            }
            else {
                switch (list.getItem(pos)->getId()) {
                    case ID_HAND:
                        temp = player_.remHand();
                        break;
                    case ID_HEAD:
                        temp = player_.remHead();
                        break;
                    case ID_BODY:
                        temp = player_.remBody();
                        break;
                    case ID_LEGS:
                        temp = player_.remLegs();
                        break;
                    case ID_FEET:
                        temp = player_.remFeet();
                        break;
                    default:
                        break;
                }
                list.remItem(pos);
                // Add temp to the inventory.
                player_.getInv()->addItem(temp);
            }
        }
    } while (pos != -1);
    
    // Remove items or they get deleted.
    while (!list.isEmpty())
        list.remItem(0);
}

void World::pickupCommand()
{
    Coord pc = player_.coord();
    ItemList * list = map_->tile(pc.getX(), pc.getY())->getItems();
    
    if (list->numItems() == 1) {
        if (!player_.getInv()->isFull())
            player_.getInv()->addItem(list->remItem(0));
    }
    else if (list->numItems() > 1) {
        if (!player_.getInv()->isFull()) {
            int pos;
            do {
                pos = selectItem(list, "= Select an Item, press ENTER to pickup =");
                if (pos >= 0 && pos < list->numItems())
                    player_.getInv()->addItem(list->remItem(pos));
            } while (pos != -1);
        }
    }
}

void World::dropCommand()
{
    Coord pc = player_.coord();
    ItemList * list = map_->tile(pc.getX(), pc.getY())->getItems();
    
    if (!player_.getInv()->isEmpty()) {
        int pos;
        do {
            pos = selectItem(player_.getInv(), "= Select an Item, press ENTER to drop =");
            if (pos >= 0 && pos < player_.getInv()->numItems())
                list->addItem(player_.getInv()->remItem(pos));
        } while (pos != -1);
    }
    else {
        MessWindow()->addstr(0, 0, "You have nothing to drop!", White);
    }
}

void World::gatherCommand()
{
    // TODO: Implement.
}

// Uses the currently wielded tool on something.
void World::useCommand()
{
    // TODO: Implement.
}

void World::actionCommand()
{
    // TODO: Implement.
}

void World::createItem(ItemList * list)
{
    return;
}

bool World::process(Command cmd)
{
    switch (cmd) {
        case CMD_MOVEUP:
            return moveCommand(DIR_UP);
            break;
        case CMD_MOVEDOWN:
            return moveCommand(DIR_DOWN);
            break;
        case CMD_MOVELEFT:
            return moveCommand(DIR_LEFT);
            break;
        case CMD_MOVERIGHT:
            return moveCommand(DIR_RIGHT);
            break;
        case CMD_VIEWINVENTORY:
            viewinventoryCommand();
            return false; // Don't process this as a turn.
            break;
        case CMD_VIEWEQUIPPED:
            viewequippedCommand();
            return false; // Don't process this as a turn.
            break;
        case CMD_PICKUP:
            pickupCommand();
            return false; // Don't process this as a turn.
            break;
        case CMD_DROP:
            dropCommand();
            return false; // Don't process this as a turn.
            break;
        case CMD_GATHER:
            gatherCommand();
            break;
        case CMD_USE:
            useCommand();
            break;
        case CMD_REST:
            restCommand();
            break;
        case CMD_SLEEP:
            sleepCommand();
            break;
        case CMD_ACTION:
            actionCommand();
            break;
        case CMD_CREATE:
            createItem(player_.getInv());
            break;
        default:
            return false;
            break;
    }
    return true;
}

// TODO: Fix hardcoded key presses.
int World::selectItem(ItemList * list, const char * head)
{
    if (list == NULL || list->numItems() == 0)
        return -1;
    
    int cur_select = 0;

    while (at::is_running()) {
        PlayWindow()->addstr(0, 0, head, White);
        
        // Draw items.
        for (int i = 0; i < list->numItems(); ++i) {
            if (cur_select == i)
                PlayWindow()->addstr(0, i+2, list->getItem(i)->getName(), Yellow);
            else
                PlayWindow()->addstr(0, i+2, list->getItem(i)->getName(), White);
        }
        // Draw info.
        
        UpdateTui();
        ClearTui();
        
        at::key key = at::getkey();
        switch (key) {
            case at::ATK_UP: case 'k':
                --cur_select;
                if (cur_select < 0)
                    cur_select = list->numItems() - 1;
                break;
            case at::ATK_DOWN: case 'j':
                ++cur_select;
                if (cur_select >= list->numItems())
                    cur_select = 0;
                break;
            case at::ATK_RETURN:
                return cur_select;
                break;
            case at::ATK_ESCAPE: case 'q': case 'Q':
                return -1;
                break;
            default:
                break;
        }
    }
    
    return -1;
}
